/*
 * SoapUI, Copyright (C) 2004-2017 SmartBear Software
 *
 * Licensed under the EUPL, Version 1.1 or - as soon as they will be approved by the European Commission - subsequent 
 * versions of the EUPL (the "Licence"); 
 * You may not use this work except in compliance with the Licence. 
 * You may obtain a copy of the Licence at: 
 * 
 * http://ec.europa.eu/idabc/eupl 
 * 
 * Unless required by applicable law or agreed to in writing, software distributed under the Licence is 
 * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 
 * express or implied. See the Licence for the specific language governing permissions and limitations 
 * under the Licence. 
 */

package com.eviware.soapui.impl.wsdl.panels.teststeps;

import com.eviware.soapui.SoapUI;
import com.eviware.soapui.impl.support.actions.ShowOnlineHelpAction;
import com.eviware.soapui.impl.wsdl.panels.support.TestRunComponentEnabler;
import com.eviware.soapui.impl.wsdl.panels.teststeps.support.GotoTestStepsComboBoxModel;
import com.eviware.soapui.impl.wsdl.support.HelpUrls;
import com.eviware.soapui.impl.wsdl.testcase.WsdlTestRunContext;
import com.eviware.soapui.impl.wsdl.teststeps.WsdlGotoTestStep;
import com.eviware.soapui.impl.wsdl.teststeps.WsdlGotoTestStep.GotoCondition;
import com.eviware.soapui.model.ModelItem;
import com.eviware.soapui.model.iface.Response;
import com.eviware.soapui.model.support.TestRunListenerAdapter;
import com.eviware.soapui.model.testsuite.SamplerTestStep;
import com.eviware.soapui.model.testsuite.TestCaseRunContext;
import com.eviware.soapui.model.testsuite.TestCaseRunner;
import com.eviware.soapui.model.testsuite.TestStepResult;
import com.eviware.soapui.support.DocumentListenerAdapter;
import com.eviware.soapui.support.UISupport;
import com.eviware.soapui.support.components.JComponentInspector;
import com.eviware.soapui.support.components.JInspectorPanel;
import com.eviware.soapui.support.components.JInspectorPanelFactory;
import com.eviware.soapui.support.components.JUndoableTextArea;
import com.eviware.soapui.support.components.JXToolBar;
import com.eviware.soapui.support.log.JLogList;
import com.eviware.soapui.support.xml.XmlUtils;
import com.eviware.soapui.ui.support.ModelItemDesktopPanel;

import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.BorderFactory;
import javax.swing.DefaultListModel;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JSplitPane;
import javax.swing.JTextArea;
import javax.swing.ListSelectionModel;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.swing.text.Document;
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.util.Date;

/**
 * DesktopPanel for WsdlGotoTestSteps
 *
 * @author Ole.Matzura
 */

public class GotoStepDesktopPanel extends ModelItemDesktopPanel<WsdlGotoTestStep> {
    private final WsdlGotoTestStep gotoStep;
    private DefaultListModel listModel;
    private JList conditionList;
    private JTextArea expressionArea;
    private JButton copyButton;
    private JButton deleteButton;
    private JButton declareButton;
    private GotoTestStepsComboBoxModel testStepsModel;
    private JComboBox testStepsCombo;
    private JButton testConditionButton;
    private TestRunComponentEnabler componentEnabler;
    private GotoCondition currentCondition;
    private JButton renameButton;
    private JButton runButton;
    private JButton addButton;
    private JLogList logList;
    private InternalTestRunListener testRunListener = new InternalTestRunListener();
    private JInspectorPanel inspectorPanel;

    public GotoStepDesktopPanel(WsdlGotoTestStep testStep) {
        super(testStep);
        this.gotoStep = testStep;
        componentEnabler = new TestRunComponentEnabler(testStep.getTestCase());
        gotoStep.getTestCase().addTestRunListener(testRunListener);

        buildUI();
    }

    public TestRunComponentEnabler getComponentEnabler() {
        return componentEnabler;
    }

    public JList getConditionList() {
        return conditionList;
    }

    public JButton getCopyButton() {
        return copyButton;
    }

    public JButton getDeclareButton() {
        return declareButton;
    }

    public JButton getDeleteButton() {
        return deleteButton;
    }

    public JTextArea getExpressionArea() {
        return expressionArea;
    }

    public WsdlGotoTestStep getGotoStep() {
        return gotoStep;
    }

    public DefaultListModel getListModel() {
        return listModel;
    }

    public JButton getTestConditionButton() {
        return testConditionButton;
    }

    public JComboBox getTestStepsCombo() {
        return testStepsCombo;
    }

    public GotoTestStepsComboBoxModel getTestStepsModel() {
        return testStepsModel;
    }

    private void buildUI() {
        JSplitPane splitPane = UISupport.createHorizontalSplit();
        splitPane.setLeftComponent(buildConditionList());

        splitPane.setRightComponent(buildExpressionArea());
        splitPane.setResizeWeight(0.1);
        splitPane.setDividerLocation(120);

        inspectorPanel = JInspectorPanelFactory.build(splitPane);
        inspectorPanel.addInspector(new JComponentInspector<JComponent>(buildLog(), "Log",
                "A log of evaluated conditions", true));

        add(inspectorPanel.getComponent(), BorderLayout.CENTER);

        setBorder(BorderFactory.createEmptyBorder(3, 3, 3, 3));
        setPreferredSize(new Dimension(550, 300));

        if (listModel.getSize() > 0) {
            conditionList.setSelectedIndex(0);
        }

        componentEnabler.add(conditionList);
        componentEnabler.add(expressionArea);
        componentEnabler.add(testStepsCombo);
        componentEnabler.add(testConditionButton);
        componentEnabler.add(copyButton);
        componentEnabler.add(declareButton);
        componentEnabler.add(deleteButton);
        componentEnabler.add(addButton);
        componentEnabler.add(runButton);
        componentEnabler.add(renameButton);
    }

    private JComponent buildLog() {
        logList = new JLogList("Condition Log for [" + getModelItem().getName() + "]");
        return logList;
    }

    private JPanel buildExpressionArea() {
        expressionArea = new JUndoableTextArea();
        expressionArea.setEnabled(false);
        expressionArea.getDocument().addDocumentListener(new SourceAreaDocumentListener());

        JPanel expressionPanel = new JPanel(new BorderLayout());
        JScrollPane scrollPane = new JScrollPane(expressionArea);
        UISupport.addTitledBorder(scrollPane, "Condition XPath Expression");

        expressionPanel.add(scrollPane, BorderLayout.CENTER);
        expressionPanel.add(buildConditionToolbar(), BorderLayout.NORTH);
        expressionPanel.add(buildTargetToolbar(), BorderLayout.SOUTH);
        return expressionPanel;
    }

    private JPanel buildConditionList() {
        listModel = new DefaultListModel();

        for (int c = 0; c < gotoStep.getConditionCount(); c++) {
            listModel.addElement(gotoStep.getConditionAt(c).getName());
        }

        conditionList = new JList(listModel);
        conditionList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
        conditionList.addListSelectionListener(new ConditionListSelectionListener());

        JScrollPane listScrollPane = new JScrollPane(conditionList);
        UISupport.addTitledBorder(listScrollPane, "Conditions");

        JPanel p = new JPanel(new BorderLayout());
        p.add(buildConditionListToolbar(), BorderLayout.NORTH);
        p.add(listScrollPane, BorderLayout.CENTER);
        return p;
    }

    private Component buildConditionListToolbar() {
        JXToolBar toolbar = UISupport.createSmallToolbar();

        addButton = UISupport.createToolbarButton(new AddAction());
        toolbar.addFixed(addButton);
        copyButton = UISupport.createToolbarButton(new CopyAction());
        copyButton.setEnabled(false);
        toolbar.addFixed(copyButton);
        deleteButton = UISupport.createToolbarButton(new DeleteAction());
        deleteButton.setEnabled(false);
        toolbar.addFixed(deleteButton);
        renameButton = UISupport.createToolbarButton(new RenameAction());
        renameButton.setEnabled(false);
        toolbar.addFixed(renameButton);
        return toolbar;
    }

    private Component buildConditionToolbar() {
        JXToolBar toolbar = UISupport.createSmallToolbar();

        declareButton = UISupport.createToolbarButton(new DeclareNamespacesAction());
        declareButton.setEnabled(false);
        toolbar.addFixed(declareButton);
        runButton = UISupport.createToolbarButton(new RunAction());
        toolbar.addFixed(runButton);

        toolbar.addGlue();
        toolbar.addFixed(UISupport.createToolbarButton(new ShowOnlineHelpAction(HelpUrls.GOTOSTEPEDITOR_HELP_URL)));
        return toolbar;
    }

    protected JXToolBar buildTargetToolbar() {
        JXToolBar builder = UISupport.createSmallToolbar();
        testStepsModel = new GotoTestStepsComboBoxModel(gotoStep.getTestCase(), null);
        testStepsCombo = new JComboBox(testStepsModel);
        testStepsCombo.setToolTipText("The step the test case will go to if the current condition is true");
        testStepsCombo.setEnabled(false);
        UISupport.setFixedSize(testStepsCombo, 280, 20);
        builder.addFixed(new JLabel("<html><b>Target step:</b></html>"));
        builder.addRelatedGap();
        builder.addFixed(testStepsCombo);
        builder.addGlue();
        testConditionButton = new JButton(new TestConditionAction());
        testConditionButton.setEnabled(false);
        builder.addFixed(testConditionButton);
        builder.setBorder(BorderFactory.createEmptyBorder(3, 3, 3, 3));
        return builder;
    }

    private final class SourceAreaDocumentListener extends DocumentListenerAdapter {
        @Override
        public void update(Document document) {
            int ix = conditionList.getSelectedIndex();
            if (ix != -1) {
                gotoStep.getConditionAt(ix).setExpression(expressionArea.getText());
            }
        }
    }

    private final class ConditionListSelectionListener implements ListSelectionListener {
        public void valueChanged(ListSelectionEvent e) {
            int ix = conditionList.getSelectedIndex();
            if (ix == -1) {
                expressionArea.setText("");
                testStepsModel.setCondition(null);
                currentCondition = null;
            } else {
                currentCondition = gotoStep.getConditionAt(ix);
                expressionArea.setText(currentCondition.getExpression());
                testStepsModel.setCondition(currentCondition);
            }

            boolean b = ix != -1;
            enableEditComponents(b);
        }
    }

    private final class AddAction extends AbstractAction {
        public AddAction() {
            putValue(Action.SHORT_DESCRIPTION, "Adds a new Condition");
            putValue(Action.SMALL_ICON, UISupport.createImageIcon("/add.png"));
        }

        public void actionPerformed(ActionEvent e) {
            String name = UISupport.prompt("Specify name for condition", "Add Condition",
                    "Condition " + (gotoStep.getConditionCount() + 1));
            if (name == null || name.trim().length() == 0) {
                return;
            }

            gotoStep.addCondition(name);

            listModel.addElement(name);
            conditionList.setSelectedIndex(listModel.getSize() - 1);
        }
    }

    private final class CopyAction extends AbstractAction {
        public CopyAction() {
            putValue(Action.SHORT_DESCRIPTION, "Copies the selected Condition");
            putValue(Action.SMALL_ICON, UISupport.createImageIcon("/clone.png"));
        }

        public void actionPerformed(ActionEvent e) {
            int ix = conditionList.getSelectedIndex();
            GotoCondition config = gotoStep.getConditionAt(ix);

            String name = UISupport.prompt("Specify name for condition", "Copy Condition", config.getName());
            if (name == null || name.trim().length() == 0) {
                return;
            }

            GotoCondition condition = gotoStep.addCondition(name);
            condition.setExpression(config.getExpression());
            condition.setTargetStep(config.getTargetStep());
            condition.setType(config.getType());

            listModel.addElement(name);
            conditionList.setSelectedIndex(listModel.getSize() - 1);
        }
    }

    private final class DeleteAction extends AbstractAction {
        public DeleteAction() {
            putValue(Action.SMALL_ICON, UISupport.createImageIcon("/delete.png"));
            putValue(Action.SHORT_DESCRIPTION, "Deletes the selected Condition");
        }

        public void actionPerformed(ActionEvent e) {
            if (UISupport.confirm("Delete selected condition", "Delete Condition")) {
                int ix = conditionList.getSelectedIndex();

                conditionList.setSelectedIndex(-1);

                gotoStep.removeConditionAt(ix);
                listModel.remove(ix);

                if (listModel.getSize() > 0) {
                    conditionList.setSelectedIndex(ix > listModel.getSize() - 1 ? listModel.getSize() - 1 : ix);
                }
            }
        }
    }

    private final class RenameAction extends AbstractAction {
        public RenameAction() {
            putValue(Action.SMALL_ICON, UISupport.createImageIcon("/rename.gif"));
            putValue(Action.SHORT_DESCRIPTION, "Renames the selected Condition");
        }

        public void actionPerformed(ActionEvent e) {
            int ix = conditionList.getSelectedIndex();
            GotoCondition config = gotoStep.getConditionAt(ix);

            String name = UISupport.prompt("Specify name for condition", "Rename Condition", config.getName());
            if (name == null || name.trim().length() == 0) {
                return;
            }

            config.setName(name);
            listModel.setElementAt(name, ix);
        }
    }

    private final class DeclareNamespacesAction extends AbstractAction {
        public DeclareNamespacesAction() {
            putValue(Action.SMALL_ICON, UISupport.createImageIcon("/declareNs.gif"));
            putValue(Action.SHORT_DESCRIPTION, "Declare available response namespaces in condition expression");
        }

        public void actionPerformed(ActionEvent e) {
            try {
                SamplerTestStep previousStep = (SamplerTestStep) gotoStep.getTestCase().findPreviousStepOfType(gotoStep,
                        SamplerTestStep.class);

                if (previousStep != null) {
                    Response response = previousStep.getTestRequest().getResponse();
                    String xml = response == null ? null : response.getContentAsString();
                    if (XmlUtils.seemsToBeXml(xml)) {
                        expressionArea.setText(XmlUtils.declareXPathNamespaces(xml) + expressionArea.getText());
                    } else {
                        UISupport.showErrorMessage("Missing response in previous request step [" + previousStep.getName()
                                + "]");
                    }
                } else {
                    UISupport.showErrorMessage("Missing previous request step");
                }
            } catch (Exception e1) {
                SoapUI.logError(e1);
            }
        }
    }

    private final class RunAction extends AbstractAction {
        public RunAction() {
            putValue(Action.SMALL_ICON, UISupport.createImageIcon("/run_all.png"));
            putValue(Action.SHORT_DESCRIPTION, "Runs the current conditions against the previous response");
        }

        public void actionPerformed(ActionEvent e) {
            if (listModel.getSize() == 0) {
                UISupport.showErrorMessage("Missing conditions!");
                return;
            }

            SamplerTestStep previousStep = gotoStep.getTestCase().findPreviousStepOfType(gotoStep, SamplerTestStep.class);

            if (previousStep == null) {
                UISupport.showErrorMessage("Missing previous request step");
            } else {
                if (previousStep.getTestRequest().getResponse() == null
                        || !XmlUtils.seemsToBeXml(previousStep.getTestRequest().getResponse().getContentAsXml())) {
                    UISupport.showErrorMessage("Missing response in previous message");
                    return;
                }

                WsdlTestRunContext context = new WsdlTestRunContext(gotoStep);
                GotoCondition target = gotoStep.runConditions(previousStep, context);
                if (target == null) {
                    logList.addLine("No condition true for current response in [" + previousStep.getName() + "]");
                } else {
                    logList.addLine("Condition triggered for go to [" + target.getTargetStep() + "]");
                }

                inspectorPanel.setCurrentInspector("Log");
            }
        }
    }

    private final class TestConditionAction extends AbstractAction {
        public TestConditionAction() {
            putValue(Action.SMALL_ICON, UISupport.createImageIcon("/run.png"));
            putValue(Action.SHORT_DESCRIPTION,
                    "Runs the current condition against the previous response and shows the result");
        }

        public void actionPerformed(ActionEvent e) {
            SamplerTestStep previousStep = gotoStep.getTestCase().findPreviousStepOfType(gotoStep, SamplerTestStep.class);

            if (previousStep == null) {
                UISupport.showErrorMessage("Missing previous request step");
            } else {
                if (previousStep.getTestRequest().getResponse() == null
                        || !XmlUtils.seemsToBeXml(previousStep.getTestRequest().getResponse().getContentAsXml())) {
                    UISupport
                            .showErrorMessage("Missing response in previous request step [" + previousStep.getName() + "]");
                    return;
                }

                try {
                    GotoCondition condition = gotoStep.getConditionAt(conditionList.getSelectedIndex());
                    WsdlTestRunContext context = new WsdlTestRunContext(gotoStep);
                    boolean evaluate = condition.evaluate(previousStep, context);
                    if (!evaluate) {
                        UISupport.showInfoMessage("Condition not true for current response in [" + previousStep.getName()
                                + "]");
                    } else {
                        UISupport.showInfoMessage("Condition true for current response in [" + previousStep.getName() + "]");
                    }
                } catch (Exception e1) {
                    UISupport.showErrorMessage("Error checking condition: " + e1.getMessage());
                }
            }
        }
    }

    public boolean onClose(boolean canCancel) {
        componentEnabler.release();
        gotoStep.getTestCase().removeTestRunListener(testRunListener);
        testStepsModel.release();
        inspectorPanel.release();

        return release();
    }

    public JComponent getComponent() {
        return this;
    }

    public boolean dependsOn(ModelItem modelItem) {
        return modelItem == gotoStep || modelItem == gotoStep.getTestCase()
                || modelItem == gotoStep.getTestCase().getTestSuite()
                || modelItem == gotoStep.getTestCase().getTestSuite().getProject();
    }

    public GotoCondition getCurrentCondition() {
        return currentCondition;
    }

    protected void enableEditComponents(boolean b) {
        expressionArea.setEnabled(b);
        testStepsCombo.setEnabled(b);
        copyButton.setEnabled(b);
        deleteButton.setEnabled(b);
        declareButton.setEnabled(b);
        testConditionButton.setEnabled(b);
        renameButton.setEnabled(b);
    }

    private class InternalTestRunListener extends TestRunListenerAdapter {
        @Override
        public void afterStep(TestCaseRunner testRunner, TestCaseRunContext runContext, TestStepResult result) {
            if (result.getTestStep() == gotoStep) {
                logList.addLine(new Date(result.getTimeStamp()).toString() + ": " + result.getMessages()[0]);
                inspectorPanel.setCurrentInspector("Log");
            }
        }
    }
}
